package com.mjk.common.tools.encrypt;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.SM2;
import com.alibaba.fastjson.JSONObject;
import com.mjk.common.tools.logger.BizLogger;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

/**
 * 国密2Demo
 * <p>
 * TODO:
 * 需要的类库，maven管理，或者下载到例如lib目录，右键添加为库
 * com.alibaba/fastjson 1.2.83 https://mvnrepository.com/artifact/com.alibaba/fastjson/1.2.83
 * cn.hutool/hutool-core 5.8.6 https://mvnrepository.com/artifact/cn.hutool/hutool-core/5.8.6
 * cn.hutool/hutool-crypto 5.8.6 https://mvnrepository.com/artifact/cn.hutool/hutool-crypto/5.8.6
 * org.bouncycastle/bcprov-jdk15on 1.6.9 https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on/1.69
 *
 * @author oyzh
 * @since 2022/9/16
 */
public class SM2Utils {
    /**
     * sm2向量
     */
    private static final byte[] DEFAULT_SM2_IV = "1234567812345678".getBytes();
    public static void main(String[] args) {
        SM2Utils sm2Demo = new SM2Utils();

//        sm2Demo.testVerifyOther();
    }

    /**
     * 对接收的请求进行验签
     */
    public static String verify(String requestBody, String pubkey, String appSecret) {
        JSONObject requestBodyObj = getDecryptBody(JSONObject.parseObject(requestBody), appSecret);
        String signData = requestBodyObj.getString("signData");
        String plainStr = getBeSignText(requestBodyObj, appSecret);
        boolean verify = verifyOwner(plainStr, signData, pubkey);
        System.out.println("验签:" + (verify ? "成功" : "失败"));
        return requestBodyObj.getString("data");
    }

    /**
     * 生成签名
     *
     * @param plainStr 待签串
     * @return sm2签名
     */
    public static String sign(String plainStr, String privkey) {
        SM2 sm2 = new SM2(ECKeyUtil.toSm2PrivateParams(SecureUtil.decode(privkey)), null);
        sm2.setMode(SM2Engine.Mode.C1C3C2);
        byte[] signBytes = sm2.sign(plainStr.getBytes(StandardCharsets.UTF_8), DEFAULT_SM2_IV);
        return Base64.encode(signBytes);
    }

    /**
     * 公钥验签，自己生成的签名验签
     *
     * @param plainText 明文
     * @param signData  签名
     * @return 结果
     */
    public static boolean verifyOwner(String plainText, String signData, String pubkey) {
        try {
            byte[] encPub = org.bouncycastle.util.encoders.Base64.decode(pubkey);
            byte[] decodeSign = org.bouncycastle.util.encoders.Base64.decode(signData);
            byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8);
            // 生成SM2sign with sm3 签名验签算法实例
            Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), new BouncyCastleProvider());
            final BouncyCastleProvider bc = new BouncyCastleProvider();
            KeyFactory keyFact = KeyFactory.getInstance("EC", bc);
            // 根据采用的编码结构反序列化公私钥
            PublicKey publicKey = keyFact.generatePublic(new X509EncodedKeySpec(encPub));
            // 【验签】签名需要使用公钥，使用公钥 初始化签名实例
            signature.initVerify(publicKey);
            // 写入待验签的签名原文到算法中
            signature.update(plainTextBytes);
            // 验签
            boolean verify = signature.verify(decodeSign);
            System.out.println("验签结果:" + verify);
            return verify;
        } catch (Exception e) {
            BizLogger.error(e);
        }
        return false;
    }

    /**
     * 公钥验签，第三方请求
     *
     * @param plainText 明文
     * @param signData  签名
     * @return 结果
     */
    public static boolean verifyOther(String plainText, String signData, String platPubkey) {
        try {
            byte[] pubKey = SecureUtil.decode(platPubkey);
            // 使用服务端公钥进行验签
            SM2 sm2Verify = new SM2(null, ECKeyUtil.toSm2PublicParams(pubKey));
            sm2Verify.setMode(SM2Engine.Mode.C1C3C2);
            byte[] decodeSign = Base64.decode(signData);
            byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8);
            boolean verify = sm2Verify.verify(plainTextBytes, decodeSign);
            System.out.println("验签结果:" + verify);
            return verify;
        } catch (Exception e) {
            BizLogger.error(e);
        }
        return false;
    }

    /**
     * 获取解密内容
     *
     * @param requestBodyObj 请求内容
     * @param appSecret      App密钥
     * @return 结果
     */
    public static JSONObject getDecryptBody(JSONObject requestBodyObj, String appSecret) {
        String appId = requestBodyObj.getString("appId");
        String encStr = requestBodyObj.getString("encData");
        String plainData = SM4Utils.decryptDataForSM2Sign(encStr, appId, appSecret);
        requestBodyObj.put("data", plainData);
        return requestBodyObj;
    }

    /**
     * 获取待签串
     *
     * @param body 内容
     * @return 待签串
     */
    public static String getBeSignText(Map<String, Object> body, String appSecret) {
        TreeMap<Object, Object> bodyTreeMap = new TreeMap<>(body);
        if (CollectionUtil.isEmpty(bodyTreeMap)) {
            return "";
        }
        // 剔除非参签字段
        bodyTreeMap.remove("signData");
        bodyTreeMap.remove("method");
        bodyTreeMap.remove("encData");
        bodyTreeMap.remove("extra");
        StringBuilder signData = new StringBuilder();
        bodyTreeMap.forEach((k, v) -> {
            String value;
            if (Objects.isNull(v)) {
                return;
            } else if (v instanceof String) {
                value = (String) v;
            } else if (v instanceof Number) {
                value = StrUtil.str(v, StandardCharsets.UTF_8);
            } else {
                value = JSONObject.toJSONString(v);
            }
            signData.append(k).append("=").append(value).append("&");
        });
        String beSignText = signData.append("key=").append(appSecret).toString();
        System.out.println("待签串:" + beSignText);
        return beSignText;
    }
}

